Composables
是 Vue 3 中的一個重要概念,它是利用 Vue 的 Composition API 來創建和管理可重用的有狀態邏輯的函數。Composables 允許我們將組件邏輯抽象成可重用的函數,使得代碼更加模塊化和易於維護。
這邊透過計數器的案例,直接帶大家理解 Composition API composables 相對於 Options API mixins 的使用差異:
👉 Vue3 Composition API composable 計數器實作連結
在 Composition API 中,ref 用於創建響應式數據,並且需要透過 .value 來讀取和修改其值。
composables/useCounter.js:將計數器邏輯拆分到一個可組合的函數中,讓外部組件可以使用。響應式變數count
以及控制數值增減的函數add
和decrease
都回傳為具名對象。由開發者自行決定在組件的何處應用。每次調用這個函數時,才會創建獨立的響應式ref
資料,確保彼此之間不會互相影響。
import { ref } from 'vue';
export function useCounter() {
const count = ref(0);
const add = () => {
count.value += 1;
};
const decrease = () => {
count.value -= 1;
};
return { count, add, decrease };
}
components/Counter.vue:拆分含有視圖的組件,使用解構的方式匯入具名函數,使邏輯和視圖管理分離。
<script setup>
import { useCounter } from '../composables/useCounter.js';
const { count, add, decrease } = useCounter();
</script>
<template>
<div>
<p>Timer Count: {{ count }}</p>
<button @click="add">add</button>
<button @click="decrease">decrease</button>
</div>
</template>
App.vue:根組件匯入組件使用。
<script setup>
import Counter from './components/Counter.vue';
</script>
<template>
<Counter />
<Counter />
<Counter />
</template>
Options API mixins 寫法(已不被推薦,僅用來比較差異):
👉 Vue3 Options API mixins 計數器實作連結
composables/useCounter.js:Options API 使用物件來定義各種選項,而共通邏輯的部分也會透過物件來管理和傳遞。
export const useCounter = {
//...略
};
components/Counter.vue::視圖組件可以拆分出來,並透過mixins
選項引入封裝好的計數器邏輯,實現邏輯與視圖的分離。然而,mixins
在引入後,會將邏輯直接合併到原本組件的數據和方法中。當使用多個mixins
時,數據或方法可能會發生覆蓋衝突,導致維護困難。
<script>
import { useCounter } from '../composables/useCounter.js';
export default {
mixins: [useCounter],
};
</script>
<template>
<div>
<p>Timer Count: {{ count }}</p>
<button @click="add">add</button>
<button @click="decrease">decrease</button>
</div>
</template>
這邊我們將上面的案例搭配localStrage
進行本地 web 緩存。新增一個useLocalStorage.js
的檔案結合前面寫的useCounter.js
一起使用。
👉 Vue3 Composition API composable 計數器(嵌套使用 useLocalStorage)實作連結
點擊計數器能同步將值更新到本地緩存 web LocalStorage
:
composables/useLocalStorage.js:結合之前的計數器邏輯,透過useCounter.js
來管理count
的響應式狀態,並使用watch
監聽count
的變化。每當count
值改變時,會自動將其存儲到localStorage
中,以實現持久化儲存。
import { useCounter } from './useCounter.js';
import { watch } from 'vue';
export function useLocalStorage(key, initialValue = 0) {
const localStorageCount = parseInt(localStorage.getItem(key) || initialValue);
const { count, add, decrease } = useCounter(localStorageCount);
watch(count, (newVal) => {
localStorage.setItem(key, JSON.stringify({ [key]: newVal }));
});
return { count, add, decrease };
}
components/Counter.vue:從父組件接收傳入的localStorage
的 key 值和初始值,並將它們傳遞給 useLocalStorage composable 進行使用。
<script setup>
import { useLocalStorage } from '../composales/useLocalStorage.js';
const props = defineProps({
inputKey: {
default: 'testInput',
},
initialValue: {
default: 0,
},
});
const { count, add, decrease } = useLocalStorage(
props.inputKey,
props.initialValue
);
</script>
<template>
<div>
<p>Timer Count: {{ count }}</p>
<button @click="add">add</button>
<button @click="decrease">decrease</button>
</div>
</template>
發送請求與錯誤處理是開發中常見且重要的場景。這個範例來自官方文件,但在實務開發中,我認為這種情況非常容易遇到,因此值得深入了解和應用。
👉 Vue3 Composition API composable 非同步函數實作連結
composables/useFetch.js:這裡使用toValue
函數,使其可以接收ref
、computed
及普通值。如果裡面放入ref
或 computed
的時候,會取得值內部值或是computed
執行的結果。使這個useFetch
函數能有更大程度的復用性。
為了模擬真實的網絡環境,透過timeout
函數模擬請求失敗的情況。請求 API 完成後,回傳錯誤訊息和正確響應的資料,這樣組件可以根據是否發生錯誤來呈現不同的畫面。
import { ref, watchEffect, toValue } from 'vue';
export function useFetch(url) {
const data = ref(null);
const error = ref(null);
watchEffect(async () => {
data.value = null;
error.value = null;
// 可接收響應式及一般資料
const urlValue = toValue(url);
try {
await timeout();
// 請求資料
const res = await fetch(urlValue);
data.value = await res.json();
} catch (e) {
// 當請求失敗的時候,把錯誤資訊傳出去
error.value = e;
}
});
return { data, error };
}
// 模擬請求成功或是失敗的情況
function timeout() {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.3) {
resolve();
} else {
reject(new Error('Random Error'));
}
}, 300);
});
}
App.vue:這邊可以試著將 url 直接改成字串的情況下,這個useFetch
的函數還是可以正常執行。根據是否有error
來決定顯示錯誤訊息,並根據是否有data
來顯示 API 回應的資料。
<script setup>
import { ref, computed } from 'vue';
import { useFetch } from './composables/useFetch.js';
const baseUrl = 'https://jsonplaceholder.typicode.com/todos/';
const id = ref('1');
const url = computed(() => baseUrl + id.value);
const { data, error } = useFetch(url);
const retry = () => {
id.value = '';
id.value = '1';
};
</script>
<template>
Load post id:
<button v-for="i in 5" @click="id = `${i}`">{{ i }}</button>
<div v-if="error">
<p>Oops! Error encountered: {{ error.message }}</p>
<button @click="retry">Retry</button>
</div>
<div v-else-if="data">
Data loaded:
<pre>{{ data }}</pre>
</div>
<div v-else>Loading...</div>
</template>
Composables
是 Vue 3 中的重要概念,基於 Composition API 設計,為代碼重用和邏輯組織提供了更靈活、更強大的方式。相比於 Options API 的mixins
,Composables
具有以下優勢:
mixins
可能帶來的命名衝突和數據來源不明確的問題。Composables
,而不會有mixins
合併順序的困擾。通過計數器和本地存儲的例子,我們看到Composables
可以嵌套使用,實現更複雜的功能組合。而非同步函數的使用示例則展示了Composables
在處理 API 請求和錯誤處理等實際開發場景中的應用。
整體而言,Composables
為 Vue 3 應用提供了一種更具模塊化、可維護性和靈活性的代碼組織方式,特別適合處理複雜的邏輯和狀態管理需求。